﻿using System;
using System.Collections.Generic;
using System.Text;

namespace TestKontrole2 {
    public class KontrolaUtility: IKontrolaStrategy{
        private static KontrolaUtility instanca = null;
        private static double FAKTOR_KOREKCIJE = 0.04;
        private Dictionary<double, float> tablicaBDistribucije;
        private KontrolaUtility() {
        }

        public static KontrolaUtility getInstanca() {
            if (instanca == null) {
                instanca = new KontrolaUtility();
            }
            return instanca;
        }

        /// <summary>
        /// Metoda treba na temelju rizika odrediti gornju granicu stopeDevijacije
        /// Sto se cini tako da iz kr = (n povrh k)* p^k*(1-p)^(n-k) izvuce p
        /// </summary>
        /// <param name="velicinaUzorka"></param>
        /// <param name="brojOdstupanja"></param>
        /// <param name="kontrolniRizik"></param>
        /// <returns></returns>
        /// 
        public float stopaDevijacije(int velicinaUzorka, int brojOdstupanja, float kontrolniRizik) {
            napraviTablicu(velicinaUzorka, brojOdstupanja, kontrolniRizik);

            if (tablicaBDistribucije.ContainsKey(kontrolniRizik))
                return tablicaBDistribucije[kontrolniRizik];
            double devijacijaIndeks = nadjiNajblizeg(kontrolniRizik);
            return tablicaBDistribucije[devijacijaIndeks];
        }

        private double nadjiNajblizeg(float kontrolniRizik) {
            ICollection<double> listaKljuceva = tablicaBDistribucije.Keys;
            double[] niz = new double[listaKljuceva.Count];
            tablicaBDistribucije.Keys.CopyTo(niz, 0);
            Array.Sort(niz);
            int i = -1;
            int indeksPravog = 0;
            while (true) {
                i++;
                if (niz[i] < kontrolniRizik)
                    continue;
                else {
                    double razlikaVeceg = niz[i] - kontrolniRizik;
                    double razlikaManjeg = kontrolniRizik - niz[i - 1];
                    if (razlikaVeceg < razlikaManjeg) 
                        indeksPravog = i;
                    else
                        indeksPravog = i - 1;
                    return niz[indeksPravog];
                }
            }
        }


        private void napraviTablicu(int velicinaUzorka, int brojOdstupanja, float kontrolniRizik) {
            tablicaBDistribucije = new Dictionary<double, float>();
            for (float f = 0.0001F; f <= 0.25F; f = f + 0.0001F) {
                double rez = binomnaDistribucija(brojOdstupanja, velicinaUzorka, f,true);
                tablicaBDistribucije[rez] = f;
            }
        }
        /// <summary>
        /// Metoda nije implementirana jerbo je zahtjela jos vise vremena no sto joj se bilo dalo
        /// </summary>
        /// <param name="VelicinaPopulacije"></param>
        /// <param name="DopustivaStopaDevijacije"></param>
        /// <param name="OcekivanaStopaDevijacija"></param>
        /// <param name="KontrolniRizik"></param>
        /// <returns></returns>
        /// 
        public int velicinaStatistickogUzorka(int VelicinaPopulacije, float DopustivaStopaDevijacije, float OcekivanaStopaDevijacija, float KontrolniRizik) {
            int velicinaUzorka = 0;
            while (true) {
                int brojPogresaka = (int)Math.Ceiling(OcekivanaStopaDevijacija * velicinaUzorka);
                double binDis = binomnaDistribucija(brojPogresaka, velicinaUzorka, DopustivaStopaDevijacije, true);
                if (binDis < KontrolniRizik)
                    break;
                velicinaUzorka++;
            }
            velicinaUzorka = faktorKorekcije(VelicinaPopulacije, velicinaUzorka);
            return velicinaUzorka;

        }

        /// <summary>
        /// faktor korekcije mijenja velicinu uzorka ovisno o odnosu velicine uzorka i velicine populacije (frakcija uzorka)
        /// ako je odnos veci od 4% racuna se korektirana velicina uzorka prema formuli nvu = svu*sqrt(1-frakcijaUzorka)
        /// </summary>
        /// <param name="velicinaPopulacije"></param>
        /// <param name="velicinaUzorka"></param>
        /// <returns>korektirana velicina uzorka</returns>
        private int faktorKorekcije(int velicinaPopulacije, int velicinaUzorka) {
            double frakcijaIzbora = (double)velicinaUzorka / velicinaPopulacije;
            if (frakcijaIzbora > FAKTOR_KOREKCIJE) {
                int novaVelicinaUzo = (int)Math.Ceiling(velicinaUzorka * Math.Sqrt(1 - frakcijaIzbora));
                return novaVelicinaUzo;
            } else
                return velicinaUzorka;
        }

        /// <summary>
        /// Binoma distribucija za dane parametre. Kumulativna znaci da nije broj pogodaka moze biti i manji od x.
        /// A ako je kumulativna false onda vjerojatnost za tocno x pogodaka
        /// </summary>
        /// <param name="x">broj ponavljanja</param>
        /// <param name="n">broj pokusaja</param>
        /// <param name="p">vjerojatnost</param>
        /// <param name="comulative">je li kumulativna ili nije</param>
        /// <returns>vjerojatnost x (ili manje) pogodaka </returns>
        private double binomnaDistribucija(int x, int n, float p, bool comulative) {
            if (comulative) {
                double rez = 0;
                for (int i = 0; i <= x; i++) {
                    rez = rez + binomnaDistribucija(i, n, p);
                }
                return rez;
            } else {
                return binomnaDistribucija(x, n, p);
            }
        }

        private double binomnaDistribucija(int x, int n, float p) {
            return (nPovrhK(n, x) * Math.Pow(p, x) * Math.Pow(1 - p, n - x));
        }

        private double nPovrhK(int n, int k) {
            double a = (int)(faktorijel(n) / (faktorijel(k) * faktorijel(n - k)));
            return a;
        }

        private double faktorijel(int p) {
            double rez = 1;
            for (int i = 1; i <= p; i++) {
                rez = rez * i;
            }
            return rez;
        }
    }
}
